﻿using OpenCvSharp;
using OpenCvSharp.Dnn;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace FaceDotnet
{
    /// <summary>
    /// 表示人脸信息
    /// </summary>
    [DebuggerDisplay("{Mat}")]
    public class Face
    {
        const string _prototxt = "_deploy.prototxt";
        const string _caffemodel = "_res10_300x300_ssd_iter_140000.caffemodel";
        const string _t7 = "_openface.nn4.small2.v1.t7";

        /// <summary>
        /// 人脸检测网络池
        /// </summary>
        private static readonly ObjectPool<Net> netDetectPool = new ObjectPool<Net>(() =>
        {
            var prototxt = ResourceFile.Export(_prototxt);
            var caffemodel = ResourceFile.Export(_caffemodel);
            return Net.ReadNetFromCaffe(prototxt, caffemodel);
        });

        /// <summary>
        /// 特征提取网络池
        /// </summary>
        private static readonly ObjectPool<Net> netFeaturePool = new ObjectPool<Net>(() =>
        {
            var t7 = ResourceFile.Export(_t7);
            return Net.ReadNetFromTorch(t7);
        });

        /// <summary>
        /// 获取检测到的人脸矩阵
        /// </summary>
        public Mat Mat { get; }

        /// <summary>
        /// 获取检测到的人脸方框
        /// 这是相对于被检测的Mat的方框
        /// </summary>
        public Rect Rect { get; }

        /// <summary>
        /// 获取检测到的人脸可信度
        /// </summary>
        public float Confidence { get; }

        /// <summary>
        /// 人脸信息
        /// </summary>
        /// <param name="mat">已检测得到的人脸矩阵</param>
        public Face(Mat mat)
        {
            this.Mat = mat;
        }

        /// <summary>
        /// 人脸信息
        /// </summary>
        /// <param name="mat">检测到的人脸矩阵</param>
        /// <param name="rect">检测到的人脸方框</param>
        /// <param name="confidence">检测到的人脸可信度</param>
        private Face(Mat mat, Rect rect, float confidence)
        {
            this.Mat = mat;
            this.Rect = rect;
            this.Confidence = confidence;
        }

        /// <summary>
        /// 从矩阵中检测人脸
        /// 返回可信度最高的一个
        /// </summary>
        /// <param name="image">矩阵</param>
        /// <param name="minConfidence">最小可信度阈值</param>
        /// <returns></returns>
        public static Face DetectFrom(Mat image, float minConfidence = 0.7f)
        {
            var faces = DetectAllFrom(image, minConfidence);
            return faces.OrderByDescending(item => item.Confidence).FirstOrDefault();
        }

        /// <summary>
        /// 从矩阵中检测所有人脸
        /// </summary>
        /// <param name="image">矩阵</param>
        /// <param name="minConfidence">最小可信度阈值</param>
        /// <returns></returns>
        public static IEnumerable<Face> DetectAllFrom(Mat image, float minConfidence = 0.7f)
        {
            var imageHeight = image.Rows;
            var imageWidth = image.Cols;

            using var blob = CvDnn.BlobFromImage(image, 1.0, new Size(300, 300), new Scalar(104, 117, 123), false, false);
            var net = netDetectPool.Rent();

            try
            {
                net.SetInput(blob);
                using var detection = net.Forward();
                using var detectionMat = new Mat(detection.Size(2), detection.Size(3), MatType.CV_32F, detection.Ptr(0));

                for (int i = 0; i < detectionMat.Rows; i++)
                {
                    var confidence = detectionMat.At<float>(i, 2);
                    if (confidence > minConfidence)
                    {
                        var x1 = (int)(detectionMat.At<float>(i, 3) * imageWidth);
                        var y1 = (int)(detectionMat.At<float>(i, 4) * imageHeight);
                        var x2 = (int)(detectionMat.At<float>(i, 5) * imageWidth);
                        var y2 = (int)(detectionMat.At<float>(i, 6) * imageHeight);

                        var rect = new Rect(x1, y1, x2 - x1, y2 - y1);
                        yield return new Face(image[rect], rect, confidence);
                    }
                }
            }
            finally
            {
                netDetectPool.Return(net);
            }
        }

        /// <summary>
        /// 创建人脸特征
        /// </summary>
        /// <returns></returns>

        public FaceFeature CreateFeature()
        {
            using var inputBlob = CvDnn.BlobFromImage(this.Mat, 1.0 / 255, new Size(96, 96), default, true, false);
            var net = netFeaturePool.Rent();
            try
            {
                net.SetInput(inputBlob);
                var feature = net.Forward().Clone();
                return new FaceFeature(feature);
            }
            finally
            {
                netFeaturePool.Return(net);
            }
        }
    }
}
